﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace Jailu.AutoUpdater
{
    public class AutoUpdater
    {
        public AutoUpdater(string serverUrl)
        {
            if (string.IsNullOrEmpty(serverUrl))
            {
                throw new Exception("serverUrl could not be null or empty");
            }

            this.ServerUrl = serverUrl;
        }

        private string temporaryFolderName = "~temp";
        public event Action<QueueStartedEventArgs> QueueStarted;
        public event Action<QueueFinishedEventArgs> QueueFinished;
        public event Action<FileStartedEventArgs> FileStarted;
        public event Action<FileFinishedEventArgs> FileFinished;
        public event Action<FileProgressChangedEventArgs> FileProgressChanged;
        public event Action<NewVersionInstallationStartedEventArgs> NewVersionInstallationStarted;
        public event Action<NewVersionInstallationFinishedEventArgs> NewVersionInstallationFinished;

        private string ServerUrl { get; set; }

        private VersionDetails _LocalVersion;
        public VersionDetails LocalVersion
        {
            get
            {
                if (_LocalVersion == null)
                {
                    _LocalVersion = this.LoadLocalVersion();
                }

                return _LocalVersion;
            }
            internal set
            {
                _LocalVersion = value;
            }
        }

        private VersionDetails _NewVersion;
        public VersionDetails NewVersion
        {
            get
            {
                if (_NewVersion == null)
                {
                    _NewVersion = this.LoadNewVersion();
                }

                return _NewVersion;
            }
            internal set
            {
                _NewVersion = value;
            }
        }

        private VersionDetails LoadNewVersion()
        {
            var xml = string.Empty;

            HttpWebRequest request = WebRequest.Create(this.ServerUrl) as HttpWebRequest;
            var response = request.GetResponse();

            using (var stream = response.GetResponseStream())
            {
                using (var reader = new StreamReader(stream, Encoding.UTF8))
                {
                    xml = reader.ReadToEnd();
                }
            }

            response.Close();

            return new VersionDetails(xml);
        }

        private VersionDetails LoadLocalVersion()
        {
            var xml = System.IO.File.ReadAllText("AutoUpdate.xml", Encoding.UTF8);

            return new VersionDetails(xml);
        }

        public bool HasNewVersion
        {
            get
            {
                return this.NewVersion.Version != this.LocalVersion.Version;
            }
        }

        public void Update(bool autoInstallNewVersion = true)
        {
            this.CreateTemporaryFolder();

            if (this.QueueStarted != null)
            {
                this.QueueStarted(
                    new QueueStartedEventArgs()
                    {
                        TotalSize = this.NewVersion.Files.Sum(item => item.Size),
                        Files = this.NewVersion.Files,
                    }
                );
            }

            var localFiles = this.LocalVersion.Files;
            foreach (var file in this.NewVersion.Files)
            {
                var localFile = localFiles
                    .FirstOrDefault(item => 
                        item.Name.ToLower() == file.Name.ToLower() 
                        && item.Path.ToLower() == file.Path.ToLower()
                    );

                if (localFile == null 
                    || localFile.Version != file.Version 
                    || localFile.Size != file.Size)
                {
                    this.Download(file);
                }
            }

            if (this.QueueFinished != null)
            {
                this.QueueFinished(new QueueFinishedEventArgs());
            }

            if (autoInstallNewVersion)
            {
                this.InstallNewVersion();
            }
        }

        private void CreateTemporaryFolder()
        {
            if (Directory.Exists(temporaryFolderName))
            {
                Directory.Delete(temporaryFolderName, true);
            }

            Directory.CreateDirectory(temporaryFolderName);
        }

        private void Download(File file)
        {
            var start = DateTime.Now;

            if (this.FileStarted != null)
            {
                this.FileStarted(
                    new FileStartedEventArgs()
                    {
                        File = file,
                    }
                );
            }

            var request = WebRequest.Create(file.Url);
            var response = request.GetResponse();
            var length = response.ContentLength;

            var buffer = new byte[1024];
            var downloadedBytes = 0;
            var outputFile = this.GetOutputFile(file);

            using (var rs = response.GetResponseStream())
            {
                using (var fs = new FileStream(
                    outputFile,
                    FileMode.OpenOrCreate, 
                    FileAccess.Write))
                {
                    var bytes = 0;
                    while ((bytes = rs.Read(buffer, 0, (int)buffer.Length)) > 0)
                    {
                        downloadedBytes += bytes;
                        fs.Write(buffer, 0, bytes);

                        if (this.FileProgressChanged != null)
                        {
                            this.FileProgressChanged(
                                new FileProgressChangedEventArgs()
                                {
                                    File = file,
                                    DownloadedSize = downloadedBytes,
                                    Speed = Math.Round(downloadedBytes / (DateTime.Now - start).TotalMilliseconds, 2),
                                }
                            );
                        }
                    }

                    fs.Flush();
                    fs.Close();
                    rs.Close();
                }
            }

            response.Close();

            if (this.FileFinished != null)
            {
                this.FileFinished(
                    new FileFinishedEventArgs()
                    {
                        File = file,
                    }
                );
            }
        }

        private string GetOutputFile(File file)
        {
            var path = temporaryFolderName;
            if (string.IsNullOrEmpty(file.Path) || file.Path == "/")
            {
                return Path.Combine(path, file.Name);
            }

            var folders = file.Path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var folder in folders)
            {
                path = Path.Combine(path, folder);
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
            }

            return Path.Combine(path, file.Name);
        }

        private void InstallNewVersion()
        {
            if (this.NewVersionInstallationStarted != null)
            {
                this.NewVersionInstallationStarted(new NewVersionInstallationStartedEventArgs() { });
            }

            new DirectoryInfo(temporaryFolderName).CopyTo(new DirectoryInfo("."), true);
            this.UpdateLocalVersionDetails();

            if (this.NewVersionInstallationFinished != null)
            {
                this.NewVersionInstallationFinished(new NewVersionInstallationFinishedEventArgs() { });
            }
        }

        private void UpdateLocalVersionDetails()
        {
            this.NewVersion.Save("AutoUpdate.xml");
        }
    }
}
